%%%-------------------------------------------------------------------
%%% File    : iter_unique.erl
%%% Author  :  <vjache>
%%% Description : This iterator takes care for returning only those 
%%%				  items from base iterator that already have not been
%%%				  returned. It keeps a cache of already returned items.
%%%
%%% Created : 15 Jul 2009 by  <vjache>
%%%-------------------------------------------------------------------
-module(iter_unique).

%%--------------------------------------------------------------------
%% Include files
%%--------------------------------------------------------------------
-include_lib("eunit/include/eunit.hrl").

-include("../include/iter.hrl").

-define(IS_PRIMITIVE(Term),(is_integer(Term) orelse is_float(Term) orelse is_boolean(Term) orelse is_atom(Term))).


%% API
-export([new/1,next/1,open/1,close/1]).

%%====================================================================
%% API
%%====================================================================
%%--------------------------------------------------------------------
%% Function: 
%% Description:
%%--------------------------------------------------------------------
new(BaseIter) ->
	iter:from_module(?MODULE, BaseIter).

open(BaseIter) ->
	{iter:open(BaseIter),ets:new(?MODULE, [private,set])}.

next({BaseIterState,Tab}) ->
	{BaseIterState1,Item}=iter:next(BaseIterState),
	case Item of
		?EOF ->
			{{BaseIterState1,Tab},?EOF};
		_ ->
			Hash=hash(Item),
			case ets:insert_new(Tab, {Hash}) of
				false ->
					next({BaseIterState1,Tab});
				true ->
					{{BaseIterState1,Tab},Item}
			end
	end.

close({BaseIterState,Tab}) ->
	ets:delete(Tab),
	iter:close(BaseIterState).
%%====================================================================
%% Internal functions
%%====================================================================

hash(Term) when ?IS_PRIMITIVE(Term) ->
	Term;
hash({F}=Term) when ?IS_PRIMITIVE(F) ->
	Term;
hash({F1,F2}=Term) when ?IS_PRIMITIVE(F1) andalso ?IS_PRIMITIVE(F2) ->
	Term;
hash({F1,F2,F3}=Term) when 
  	  ?IS_PRIMITIVE(F1) andalso 
	  ?IS_PRIMITIVE(F2) andalso
	  ?IS_PRIMITIVE(F3) ->
	Term;
hash({F1,F2,F3,F4}=Term) when 
  	  ?IS_PRIMITIVE(F1) andalso 
	  ?IS_PRIMITIVE(F2) andalso
	  ?IS_PRIMITIVE(F3) andalso
	  ?IS_PRIMITIVE(F4) ->
	Term;
hash(Term) ->
	Binary=term_to_binary(Term),
	if
		size(Binary)>32 ->
			erlang:md5(term_to_binary(Term));
		true ->
			Term
	end.

%%====================================================================
%% Test functions
%%====================================================================
ints_test() ->
	TestData=[1,2,3,4,5,6],
	Iter=new(iter:from_list(TestData++TestData)),
	?assertEqual(lists:sort(TestData),lists:sort(iter:to_list(Iter))).
mixed_test() ->
	TestData=[{1},{2.01},2.01,abs,{4,5},6,true,now()],
	Iter=new(iter:from_list(TestData++TestData)),
	?assertEqual(lists:sort(TestData),lists:sort(iter:to_list(Iter))).
big_terms_test() ->
	TestData=["qwyguygjwfgsajhfgjsagjkafgsjadhgfjsadgjksadjgsajasgjjasjssjhg",
			  {"asbgsj"},
			  lists:seq(1,1000)],
	Iter=new(iter:from_list(TestData++TestData)),
	?assertEqual(lists:sort(TestData),lists:sort(iter:to_list(Iter))).
